package com.atguigu.gmall.index.uitls;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

@Component
public class DistributeLock {

    @Autowired
    private StringRedisTemplate redisTemplate;

    private Timer timer;

    /**
     * 加锁，可重入
     * @param lockName
     * @param uuid
     * @param expire
     * @return
     */
    public Boolean tryLock(String lockName,String uuid,Integer expire){
        String script = "if redis.call('exists', KEYS[1]) == 0 or redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   redis.call('hincrby', KEYS[1], ARGV[1], 1) " +
                "   redis.call('expire', KEYS[1], ARGV[2]) " +
                "   return 1 " +
                "else " +
                "   return 0 " +
                "end";

        Boolean flag = this.redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName),uuid,expire.toString());
        if (!flag){
            try {
                Thread.sleep(80);
                tryLock(lockName,uuid,expire);
            } catch (Exception e) {
                e.printStackTrace();
            }
        } else {
            //开启定时任务，定时续期
            this.renewTime(lockName,uuid,expire);
        }
        return true;
    }

    /**
     * 解锁：减1或删除锁
     * @param lockName
     * @param uuid
     */
    public void unlock(String lockName,String uuid){
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 0 " +
                "then " +
                "   return nil " +
                "elseif redis.call('hincrby', KEYS[1], ARGV[1], -1) == 0 " +
                "then " +
                "   return redis.call('del', KEYS[1]) " +
                "else " +
                "   return 0 " +
                "end";

        Long flag =  this.redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockName),uuid);
        if (flag == null){
            throw new RuntimeException("你要释放的锁不存在或者不属于你！");
        } else if (flag == 1){
            this.timer.cancel();
        }
    }

    /**
     * 锁延期
     * 线程等待超时时间的2/3时间后，执行锁延时代码，知道业务逻辑执行完毕
     * 因此在此过程中，其他线程无法获取到锁，保证了线程安全性
     * lockName, expire 单位：毫秒
     */
    private void renewTime(String lockName,String uuid,Integer expire){
        String script = "if redis.call('hexists', KEYS[1], ARGV[1]) == 1 " +
                "then " +
                "   return redis.call('expire', KEYS[1], ARGV[2]) " +
                "else " +
                "   return 0 " +
                "end";

        this.timer = new Timer();
        this.timer.schedule(new TimerTask() {
            @Override
            public void run() {
                redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),uuid,expire.toString());
            }
        },expire * 1000 / 3,expire * 1000 / 3);
    }

}
